use diesel::{
    dsl::Find,
    prelude::*,
    query_builder::{AsQuery, IntoUpdateTarget},
    query_dsl::methods::{ExecuteDsl, FindDsl},
};

use super::{UpdateFindStatement, UpdateFindTable};
use crate::database::DbError;

pub fn update<'a, T, V, PK>(
    conn: &mut MysqlConnection,
    table: T,
    updated_item: V,
    id: PK,
) -> Result<usize, DbError>
where
    T: FindDsl<PK>,
    Find<T, PK>: IntoUpdateTarget,
    V: AsChangeset<Target = UpdateFindTable<Find<T, PK>>>,
    UpdateFindStatement<Find<T, PK>, V::Changeset>: AsQuery + ExecuteDsl<MysqlConnection>,
{
    let affected_rows = diesel::update(table.find(id))
        .set(updated_item)
        .execute(conn)?;
    Ok(affected_rows)
}

#[macro_export]
macro_rules! db_update {
    ($table:ident, $new_model:ident, $fn:ident) => {
        pub fn $fn(
            conn: &mut MysqlConnection,
            id: i32,
            new_item: $new_model,
        ) -> Result<usize, DbError> {
            let updated_item = crud::update::update(conn, $table::table, new_item, id)?;
            Ok(updated_item)
        }
    };
}

#[macro_export]
macro_rules! test_db_update {
    ($key:ident, $value:expr) => {
        #[test]
        fn test_update() {
            let mut conn = establish_connection();
            conn.test_transaction::<_, diesel::result::Error, _>(|conn| {
                let (added_id, _, _) = insert_test_data(conn);
                let mut added_item = read(conn, added_id).unwrap();
                added_item.$key = $value;
                let updated_items = update(conn, added_id, (&added_item).into()).unwrap();
                assert_eq!(updated_items, 1);
                let updated_item = read(conn, added_id).unwrap();
                assert_eq!(updated_item.$key, $value);
                Ok(())
            });
        }
    };
}
